package org.lysh.utils.file;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.json.JSONUtil;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.lysh.dto.BucketPolicyConfigDto;
import org.lysh.dto.UploadFileDto;
import org.lysh.enums.HttpContentTypeEnum;
import org.lysh.enums.OssFolder;
import org.lysh.exception.BusinessException;
import org.lysh.utils.proteries.PropertiesUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;

/**
 * @author liyishan
 * @date 2024/4/24 15:49
 * @apiNote
 */
@Slf4j
@Component
public class MinioUtils {
    public static final String ENDPOINT = PropertiesUtil.getProperty("config", "minio.endpoint", "");

    public static final String BUCKET_NAME = PropertiesUtil.getProperty("config", "minio.bucket_name", "");
    public static final String OPEN_BUCKET_NAME = PropertiesUtil.getProperty("config", "minio.openBucketName", "");
    public static final String ACCESS_KEY = PropertiesUtil.getProperty("config", "minio.accessKey", "");
    public static final String SECRET_KEY = PropertiesUtil.getProperty("config", "minio.secretKey", "");

    /**
     * 公域文件上传
     *
     * @param file            文件
     * @param ossFolder       文件夹
     * @param fileName        文件名
     * @param httpContentType 文件类型
     * @return 文件地址
     */

    public String uploadOpenFile(File file, OssFolder ossFolder, String fileName, HttpContentTypeEnum httpContentType) {
        if (null == file) {
            log.error("上传文件为空");
            throw new BusinessException("文件为空");
        }
        // 如果文件大于200M, 则不允许上传
        if (file.length() > 200 * 1024 * 1024) {
            log.error("上传文件过大");
            throw new BusinessException("文件过大");
        }
        try {
            InputStream inputStream = Files.newInputStream(file.toPath());
            UploadFileDto uploadFileDto = UploadFileDto.builder()
                    .bucketName(OPEN_BUCKET_NAME)
                    .fileName(fileName)
                    .inputStream(inputStream)
                    .ossFolder(ossFolder)
                    .httpContentType(httpContentType)
                    .build();
            return uploadFile(uploadFileDto);
        } catch (Exception e) {
            log.error("上传文件失败", e.getMessage());
            throw new BusinessException("上传文件失败");
        }
    }

    /**
     * 私域文件上传
     *
     * @param file            文件
     * @param ossFolder       文件夹
     * @param fileName        文件名
     * @param httpContentType 文件类型
     * @return 文件地址
     */

    public String uploadFile(File file, OssFolder ossFolder, String fileName, HttpContentTypeEnum httpContentType) {
        if (null == file) {
            log.error("上传文件为空");
            throw new BusinessException("文件为空");
        }
        // 如果文件大于200M, 则不允许上传
        if (file.length() > 200 * 1024 * 1024) {
            log.error("上传文件过大");
            throw new BusinessException("文件过大");
        }
        try {
            InputStream inputStream = Files.newInputStream(file.toPath());
            UploadFileDto uploadFileDto = UploadFileDto.builder()
                    .bucketName(BUCKET_NAME)
                    .fileName(fileName)
                    .inputStream(inputStream)
                    .ossFolder(ossFolder)
                    .httpContentType(httpContentType)
                    .build();
            return uploadFile(uploadFileDto);
        } catch (Exception e) {
            log.error("上传文件失败", e.getMessage());
            throw new BusinessException("上传文件失败");
        }
    }

    /**
     * 私域文件上传
     *
     * @param bytes           文件
     * @param ossFolder       文件夹
     * @param fileName        文件名
     * @param httpContentType 文件类型
     * @return 文件地址
     */

    public String uploadFile(byte[] bytes, OssFolder ossFolder, String fileName, HttpContentTypeEnum httpContentType) {
        if (bytes.length <= 0) {
            log.error("上传文件为空");
            throw new BusinessException("文件为空");
        }
        try {
            ByteArrayInputStream bis = IoUtil.toStream(bytes);
            UploadFileDto uploadFileDto = UploadFileDto.builder()
                    .bucketName(BUCKET_NAME)
                    .fileName(fileName)
                    .inputStream(bis)
                    .ossFolder(ossFolder)
                    .httpContentType(httpContentType)
                    .build();
            return uploadFile(uploadFileDto);
        } catch (Exception e) {
            log.error("上传文件失败", e.getMessage());
            throw new BusinessException("上传文件失败");
        }
    }

    /**
     * 私域文件上传
     *
     * @param file            文件
     * @param ossFolder       文件夹
     * @param httpContentType 文件类型
     * @return 文件地址
     */

    public String uploadFile(MultipartFile file, OssFolder ossFolder, HttpContentTypeEnum httpContentType) {
        if (null == file) {
            log.error("上传文件为空");
            throw new BusinessException("文件为空");
        }
        try {
            UploadFileDto uploadFileDto = UploadFileDto.builder()
                    .bucketName(BUCKET_NAME)
                    .fileName(file.getOriginalFilename())
                    .inputStream(file.getInputStream())
                    .ossFolder(ossFolder)
                    .httpContentType(httpContentType)
                    .build();
            return uploadFile(uploadFileDto);
        } catch (Exception e) {
            log.error("上传文件失败", e.getMessage());
            throw new BusinessException("上传文件失败");
        }
    }

    public String uploadFile(UploadFileDto uploadFileDto) {
        String bucketName = uploadFileDto.getBucketName();
        String fileName = uploadFileDto.getFileName();
        InputStream inputStream = uploadFileDto.getInputStream();
        OssFolder ossFolder = uploadFileDto.getOssFolder();
        HttpContentTypeEnum httpContentType = uploadFileDto.getHttpContentType();
        try {
            MinioClient minioClient = MinioClient.builder()
                    .endpoint(ENDPOINT)
                    .credentials(ACCESS_KEY, SECRET_KEY)
                    .build();
            boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (isExist) {
                log.info("存储桶已经存在！");
            } else {
                //创建存储桶并设置只读权限
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                BucketPolicyConfigDto bucketPolicyConfigDto = createBucketPolicyConfigDto(bucketName);
                SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder()
                        .bucket(bucketName)
                        .config(JSONUtil.toJsonStr(bucketPolicyConfigDto))
                        .build();
                minioClient.setBucketPolicy(setBucketPolicyArgs);
            }
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            // 设置存储对象名称
            String objectName = ossFolder.getFolder() + sdf.format(new Date()) + "/" + fileName;
            // 使用putObject上传一个文件到存储桶中
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .contentType(httpContentType.getContentType())
                    .stream(inputStream, inputStream.available(), ObjectWriteArgs.MIN_MULTIPART_SIZE).build();
            minioClient.putObject(putObjectArgs);
            log.info("文件上传成功!");
            return ENDPOINT + "/" + bucketName + objectName;
        } catch (Exception e) {
            log.error("上传文件失败", e.getMessage());
            throw new BusinessException("文件处理异常");
        }
    }

    /**
     * 创建存储桶的访问策略，设置为只读权限
     */
    private BucketPolicyConfigDto createBucketPolicyConfigDto(String bucketName) {
        BucketPolicyConfigDto.Statement statement = BucketPolicyConfigDto.Statement.builder()
                .Effect("Allow")
                .Principal("*")
                .Action("s3:GetObject")
                .Resource("arn:aws:s3:::" + bucketName + "/*.**").build();
        return BucketPolicyConfigDto.builder()
                .Version("2012-10-17")
                .Statement(CollUtil.toList(statement))
                .build();
    }


    /**
     * 下载文件
     *
     * @param url
     * @param httpContentType
     * @param response
     * @throws IOException
     */
    public void downloadFile(String url, HttpContentTypeEnum httpContentType,
                             boolean open,
                             HttpServletRequest request,
                             HttpServletResponse response) throws UnsupportedEncodingException {
        String fileName = parseFileName(url);
        //下载文件
        byte[] fileBytes = downloadFileToByte(url);
        //是否打开文件
        String contentDisposition = open ? "inline;" : "attachment;";
        response.reset();
        String header = request.getHeader("user-agent");
        if (StringUtils.isNotBlank(header)) {
            if (header.toUpperCase().contains("FIREFOX")) {
                //兼容火狐浏览器
                response.addHeader("Content-Disposition", contentDisposition + "filename=" + new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            } else if (header.toUpperCase().contains("SAFARI")) {
                response.setHeader("content-Disposition", contentDisposition + "filename*=UTF-8''" + URLEncoder.encode(fileName, "UTF-8"));
            }
        } else {
            //其他浏览器谷歌等
            response.addHeader("Content-Disposition", contentDisposition + "filename=" + URLEncoder.encode(fileName, "UTF-8"));
        }
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
        response.addHeader("Content-Length", "" + fileBytes.length);
        // 使用.png的另一种HttpContentType image/png 因为发现枚举里的不能直接查看,又怕改枚举影响其他地方，所以这里只改的代码
        if (HttpContentTypeEnum.PNG.equals(httpContentType)) {
            response.setContentType("image/png");
        } else {
            response.setContentType(httpContentType.getContentType());
        }
        try (BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
            outputStream.write(fileBytes);
            outputStream.flush();
        } catch (IOException e) {
            log.error("文件输出异常:", e);
            throw new BusinessException("文件输出异常");
        }
    }

    /**
     * 下载为byte or base64
     *
     * @param url
     * @return
     */
    public byte[] downloadFileToByte(String url) {
        InputStream inputStream = downloadFile(url, BUCKET_NAME);
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buff = new byte[1024];
            int rc;
            while ((rc = inputStream.read(buff, 0, 1024)) > 0) {
                outputStream.write(buff, 0, rc);
            }
            //字节整理完毕
            return outputStream.toByteArray();
        } catch (Exception e) {
            log.error("文件输出异常:", e);
            throw new BusinessException("文件输出异常");
        }
    }

    /**
     * 下载为byte or base64
     *
     * @param url
     * @return
     */
    public String downloadFile(String url) {
        InputStream inputStream = downloadFile(url, BUCKET_NAME);
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buff = new byte[1024];
            int rc;
            while ((rc = inputStream.read(buff, 0, 1024)) > 0) {
                outputStream.write(buff, 0, rc);
            }
            //字节整理完毕
            byte[] fileByte = outputStream.toByteArray();
            return Base64.getEncoder().encodeToString(fileByte);
        } catch (Exception e) {
            log.error("文件输出异常:", e);
            throw new BusinessException("文件输出异常");
        }
    }


    /**
     * 下载文件
     *
     * @param url url路径
     * @return InputStream
     */
    public InputStream downloadFile(String url, String bucketName) {
        try {
            MinioClient minioClient = MinioClient.builder()
                    .endpoint(ENDPOINT)
                    .credentials(ACCESS_KEY, SECRET_KEY)
                    .build();
            String objectName = url.substring(url.indexOf(bucketName) + bucketName.length() + 1);

            // 下载文件
            InputStream inputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build());
            return inputStream;
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("文件下载失败");
        }
    }

    /**
     * 解析文件名称
     *
     * @param url 全地址
     * @return 文件名
     */
    public String parseFileName(String url) {
        String[] split = url.split("/");
        return split[split.length - 1];
    }
}
